﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NF.Core;
using NF.Core.Fuzzy;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;

namespace NF.Approximation
{
    class Program
    {
        static double minF1 = 1.0;
        static double minF2 = -4.0;
        static double maxF1 = 5.0;
        static double maxF2 = 6.0;

        static void GenerateRuleMatrixImage(String image, FuzzyController c)
        {
            int[] d = c.GetRuleMatrixDimensions();
            using (Bitmap b = new Bitmap(d[0], d[1], System.Drawing.Imaging.PixelFormat.Format24bppRgb))
            {
                BitmapData bmd = b.LockBits(new Rectangle(0, 0, d[0], d[1]), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

                unsafe
                {
                    void* scan0 = (void*)bmd.Scan0;
                    byte* line = (byte*)scan0;
                    byte* p;

                    for (int j = 0; j < d[1]; ++j)
                    {
                        p = line;

                        for (int i = 0; i < d[0]; ++i, p += 3)
                        {
                            if (c.GetRuleFromMatrix(i, j) == null)
                                p[0] = p[1] = p[2] = 0;
                            else
                                p[0] = p[1] = p[2] = 255;
                        }

                        line += bmd.Stride;
                    }
                }

                b.UnlockBits(bmd);

                b.Save(image);
            }
        }

        static double F(double x, double y)
        {
            return x * x + y * y;
        }

        static double fuzzyF1(double x, double y)
        {
            return Math.Pow((1 + Math.Pow(x, -2) + Math.Pow(y, -1.5)), 2);
        }

        static double fuzzyF2(double x, double y)
        {
            return 0.1 * x + 0.05 * Math.Pow(x + y, 2) + 3 * Math.Pow(1 - x, 2) * 
                Math.Exp(-Math.Pow(x, 2) - Math.Pow(y + 1, 2)) - 10 * 
                (x / 5 - Math.Pow(x, 3) - Math.Pow(y, 5)) * 
                Math.Exp(-Math.Pow(x, 2) - Math.Pow(y, 2)) - 
                (Math.Exp(-Math.Pow(x + 1, 2) - Math.Pow(x, 2))) / 3;
        }

        private delegate double Funkcja(double x, double y);

        const int GridSize = 20;
        const int K = 5;
        const int TestIterations = 100;

        static void Main(string[] args)
        {
            FuzzyController ctrl1 = generateControllerFromSamples(minF1, maxF1, fuzzyF1);
            FuzzyController ctrl2 = generateControllerFromSamples(minF2, maxF2, fuzzyF2);
            approximate(ctrl1, minF1, maxF1, fuzzyF1);
            approximate(ctrl2, minF2, maxF2, fuzzyF2);
        }

        private static FuzzyController generateControllerFromSamples(double min, double max, Funkcja f)
        {
            List<NPoint> samples = new List<NPoint>(GridSize * GridSize);
            double x, y;

            for (int j = 0; j <= GridSize; ++j)
            {
                for (int i = 0; i <= GridSize; ++i)
                {
                    x = (double)i / (double)GridSize;
                    x = x * (max - min) + min;
                    y = (double)j / (double)GridSize;
                    y = y * (max - min) + min;

                    samples.Add(new NPoint(x, y, f(x, y)));
                }
            }

            FuzzyController kontroler = new FuzzyController(samples, K);

            return kontroler;
        }

        private static void approximate(FuzzyController fc, double min, double max, Funkcja f)
        {
            double x, y;
            Random rand = new Random(666);

            GenerateRuleMatrixImage(f.Method.Name + "_k_" + K + ".png", fc);

            double actual, approx;

            double[] mse = new double[TestIterations];
            double[] mae = new double[TestIterations];
            StreamWriter sw = File.CreateText(f.Method.Name + "_k_" + K + ".csv");
            sw.WriteLine("X;Y;f actual;f approx;MSE;MAE");

            for (int i = 0; i < TestIterations; ++i)
            {
                x = rand.NextDouble() * (max - min) + min;
                y = rand.NextDouble() * (max - min) + min;

                actual = f(x, y);
                approx = fc.Evaluate(new NPoint(x, y));

                mse[i] = Math.Pow(actual - approx, 2.0f);
                mae[i] = Math.Abs(actual - approx);

                sw.WriteLine("{0};{1};{2};{3};{4};{5}", x, y, actual, approx, mse[i], mae[i]);
            }

            double avgMse = mse.Average(), avgMae = mae.Average();
            sw.WriteLine("# avgMse={0} avgMae={1}", avgMse, avgMae);

            sw.Close();
        }
    }
}
